/**
 * Copyright (c) 2011 Technical Centre Radio Systems Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ua.tcrs.tabula.container;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.AccessibleObject;
import java.util.ArrayList;
import java.util.Map;
import java.util.Hashtable;
import java.util.List;
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

import ua.tcrs.tabula.aop.AopBinder;
import ua.tcrs.tabula.util.Iterables;
import ua.tcrs.tabula.util.Predicate;
import ua.tcrs.tabula.util.Function;
import ua.tcrs.tabula.Subscribe;
import ua.tcrs.tabula.Dispatcher;

/**
 * ServiceRegistry with AOP features
 * Simple implementation
 * TODO: add faster interceptors lookup for Method in Handler.invoke()
 */

public class AopServiceRegistry extends BaseServiceRegistry implements AopBinder {
	public AopServiceRegistry( ServiceFactory parent ) {
		super( parent );
		addService( AopBinder.class, this );
		//Dispatcher.subscribeAnnotated( this );
	}
	
	static class InterceptorBinding {
		InterceptorBinding( Predicate<? super Class<?>> classFilter, Predicate<? super Method> methodFilter, MethodInterceptor interceptor ) {
			this.classFilter = classFilter;
			this.methodFilter = methodFilter;
			this.interceptor = interceptor;
		}
		Predicate<? super Class<?>> classFilter;
		Predicate<? super Method> methodFilter;
		MethodInterceptor interceptor;
	}
	List<InterceptorBinding> bindings = new CopyOnWriteArrayList<InterceptorBinding>();
	/*
	@Subscribe( group = "tabula", event = "inited" )
	protected void bindInterceptors() {
		System.out.println( "bindInterceptors()" );
		binded = new Hashtable<Class<?>,List<BindedInterceptor>>();
		rebindInterceptors();
	}
	*/
	/*
	private void rebindInterceptors() {
		if ( binded == null ) return; 
		for ( UnbindedInterceptor intr: unbinded ) {
			for ( Class<?> iface: Iterables.filter( services.keySet(), intr.classFilter ) ) {
				List<BindedInterceptor> list = binded.get( iface );
				if ( list == null ) {
					list = new ArrayList<BindedInterceptor>();
					binded.put( iface, list );
				}
				list.add( intr );
			}
		}
		unbinded.clear();
	}
	*/
	class Handler implements InvocationHandler {
		Object target;
		Handler( Object target ) {
			this.target = target;
		}
		public Object invoke( final Object proxy, final Method method, final Object[] args ) throws Throwable {
			if ( method.getDeclaringClass() == Object.class ) {
				// Handle the Object public methods.
				String methodName = method.getName();
				if ( methodName.equals("hashCode") ) {
					return new Integer( System.identityHashCode(proxy) );
				} else if ( methodName.equals("equals") ) {
					return ( proxy == args[0] ? Boolean.TRUE : Boolean.FALSE );
				} else if ( methodName.equals("toString") ) {
					return proxy.getClass().getName()+'|'+target.toString();
				}
			}
			
			class MethodInvocationChain implements MethodInvocation {
				Iterator<MethodInterceptor> chain;
				MethodInvocationChain( Iterable<MethodInterceptor> chain ) {
					this.chain = chain.iterator();
				}
				public Method getMethod() { return method; }
				public Object[] getArguments() { return args; }
				public AccessibleObject getStaticPart() { return method; }
				public Object getThis() { return target; }
				public Object proceed() throws java.lang.Throwable {
					if ( chain.hasNext() ) {
						MethodInterceptor interceptor = chain.next();
						return interceptor.invoke( this );
					} else {
						try {
							method.setAccessible( true ); // Without this: can not access a member of class XX with modifiers "public abstract"
							return method.invoke( target, args );
						} catch ( InvocationTargetException ex ) {
							// Invoked method threw a checked exception.
							// Must rethrow it. The client won't see the interceptor.
							throw ex.getCause();
						}
					}
				}
			}
			
			return new MethodInvocationChain( getMethodInterceptors( method ) ).proceed();
		}
		Iterable<MethodInterceptor> getMethodInterceptors( final Method method ) {
			return Iterables.transform( getInterceptorBindings( method ), new Function<InterceptorBinding,MethodInterceptor>() {
				public MethodInterceptor apply( InterceptorBinding binding ) {
					return binding.interceptor;
				}
			} );
		}
		Iterable<InterceptorBinding> getInterceptorBindings( final Method method ) {
			return Iterables.filter( bindings, new Predicate<InterceptorBinding>() {
				public boolean apply( InterceptorBinding binding ) {
					return binding.classFilter.apply( method.getDeclaringClass() ) && binding.methodFilter.apply( method );
				}
			} );
		}
	}
	@Override
	protected Object wrapService( Object service, Iterable<Class<?>> ifaces ) {
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		InvocationHandler handler = new Handler( service );
		Proxy proxy = (Proxy)Proxy.newProxyInstance( classLoader, Iterables.toList( ifaces ).toArray( new Class<?>[]{} ), handler );
		return proxy;
	}
	@Override
	public void bindInterceptor( Predicate<? super Class<?>> classFilter, Predicate<? super Method> methodFilter, MethodInterceptor interceptor ) {
		bindings.add( new InterceptorBinding( classFilter, methodFilter, interceptor ) );
		//rebindInterceptors();
	}
}

